use once_cell::sync::Lazy;

use crate::utils::time::CacheTime;

pub mod file_output;

#[allow(dead_code)]
pub static mut GLOBAL_LOGGER: Lazy<Logger> = Lazy::new(Logger::new);
static GLOBAL_LOG: Log = Log {};

#[allow(dead_code)]
#[derive(PartialEq, PartialOrd, Clone, Copy, Debug)]
#[repr(u8)]
pub enum Level {
    Debug = 1,
    Info = 2,
    Warn = 3,
    Error = 4,
}

#[allow(dead_code)]
pub fn to_level_tag(level: Level) -> &'static str {
    match level {
        Level::Debug => "[Debug]",
        Level::Info => "[Info]",
        Level::Warn => "[Warn]",
        Level::Error => "[Error]",
    }
}

pub trait LogOutput: Sync + Send {
    fn print(&self, msg: &str);
    fn flush(&self);
    fn stop(&self);
}

pub struct Logger {
    pub log_level: Level,
    output: Option<Box<dyn LogOutput>>,
    enable_console: bool,
}

#[allow(dead_code)]
impl Logger {
    pub fn new() -> Self {
        Logger {
            log_level: Level::Debug,
            output: None,
            enable_console: true,
        }
    }

    pub fn set_level(&mut self, level: Level) {
        self.log_level = level;
    }

    pub fn set_console(&mut self, enable: bool) {
        self.enable_console = enable;
    }

    pub fn set_output(&mut self, output: Box<dyn LogOutput>) {
        self.output = Some(output);
    }

    pub fn print(&self, msg: &str) {
        if let Some(output) = &self.output {
            output.print(msg);
        }

        if self.enable_console {
            print!("{}", msg);
        }
    }

    pub fn flush(&self) {
        if let Some(output) = &self.output {
            output.flush();
        }
    }

    pub fn stop(&self) {
        if let Some(output) = &self.output {
            output.stop();
        }
    }
}

// Log实际操作的是GLOBAL_LOGGER.
pub struct Log {}

#[allow(dead_code)]
impl Log {
    pub fn flush(&self) {
        unsafe {
            GLOBAL_LOGGER.flush();
        }
    }

    pub fn log_flush() {
        unsafe {
            GLOBAL_LOGGER.flush();
        }
    }

    pub fn stop() {
        unsafe {
            GLOBAL_LOGGER.stop();
        }
    }

    pub fn set_console(enable: bool) {
        unsafe {
            GLOBAL_LOGGER.enable_console = enable;
        }
    }

    pub fn set_output(output: Box<dyn LogOutput>) {
        unsafe {
            GLOBAL_LOGGER.set_output(output);
        }
    }

    pub fn set_level(level: Level) {
        unsafe {
            GLOBAL_LOGGER.set_level(level);
        }
    }
}

#[allow(dead_code)]
pub fn use_alogger() {
    log::set_max_level(log::LevelFilter::Trace);
    _ = log::set_logger(&GLOBAL_LOG);
}

impl log::Log for Log {
    fn enabled(&self, _: &log::Metadata) -> bool {
        true
    }

    fn log(&self, record: &log::Record) {
        let level = match record.level() {
            log::Level::Error => Level::Error,
            log::Level::Warn => Level::Warn,
            log::Level::Info => Level::Info,
            log::Level::Debug => Level::Debug,
            log::Level::Trace => Level::Debug,
        };

        unsafe {
            if level >= GLOBAL_LOGGER.log_level {
                let log_time = CacheTime::get_log_time();
                let level_tag = to_level_tag(level);
                let file = record.file().unwrap_or_default();
                let line = record.line().unwrap_or_default();

                let msg = format!(
                    "{} {:8} {}:{}\t{}\n",
                    log_time,
                    level_tag,
                    file,
                    line,
                    record.args()
                );

                GLOBAL_LOGGER.print(&msg);
            }
        }
    }

    fn flush(&self) {
        self.flush();
    }
}

#[macro_export]
macro_rules! alog_debug {
    () => {};
    ($($arg:expr),*) => {
        $crate::alog_common!($crate::alog::Level::Debug, $($arg),*);
    };
}

#[macro_export]
macro_rules! alog_info {
    () => {};
    ($($arg:expr),*) => {
        $crate::alog_common!($crate::alog::Level::Info, $($arg),*);
    };
}

#[macro_export]
macro_rules! alog_warn {
    () => {};
    ($($arg:expr),*) => {
        $crate::alog_common!($crate::alog::Level::Warn, $($arg),*);
    };
}

#[macro_export]
macro_rules! alog_error {
    () => {};
    ($($arg:expr),*) => {
        $crate::alog_common!($crate::alog::Level::Error, $($arg),*);
    };
}

#[macro_export]
macro_rules! alog_common {
    ($level:expr, $($arg:expr),*) => {
        unsafe {
            if $level >= $crate::alog::GLOBAL_LOGGER.log_level {
                let log_time = $crate::utils::time::CacheTime::get_log_time();
                let level_tag = $crate::alog::to_level_tag($level);
                let msg = format!("{} {:8} {}:{}\t{}\n",log_time,level_tag, file!(), line!(), format!($($arg),*));
                $crate::alog::GLOBAL_LOGGER.print(&msg);
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use std::{sync::atomic::AtomicUsize, time::Duration};

    use tokio::time::sleep;

    use crate::alog::file_output::{FileOutput, FileOutputOptions, Rolling};
    use crate::alog::{use_alogger, Log};
    use crate::runtime::RuntimeManager;

    #[test]
    fn test_logger() {
        RuntimeManager::init_main_runtime(1);
        RuntimeManager::init_worker_runtime(None, 4);

        RuntimeManager::main_runtime_block_on(async {
            let mut opts = FileOutputOptions::default();
            opts.rolling = Rolling::Minute;
            let file_output =
                FileOutput::new(Some(opts), "/tmp/test.log".to_string())
                    .unwrap();

            Log::set_output(Box::new(file_output));
            Log::set_console(true);
            use_alogger();

            static TASKS: AtomicUsize = AtomicUsize::new(10);

            for x in 0..10 {
                RuntimeManager::get_worker_runtime().get_handle().spawn(
                    async move {
                        for y in 0..100 {
                            log::warn!("{} {}", x, y);
                            alog_warn!("{} {}", x, y);
                            sleep(Duration::from_millis(1)).await;
                        }
                        TASKS
                            .fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
                    },
                );
            }

            loop {
                if TASKS.load(std::sync::atomic::Ordering::Relaxed) == 0 {
                    break;
                }
                sleep(Duration::from_millis(100)).await;
            }

            Log::stop();
        });
    }
}
